fix(workflows): init step must not replace init's own error with SystemExit: 1 - #4530
Open
jawwad-ali wants to merge 1 commit into
Open
fix(workflows): init step must not replace init's own error with SystemExit: 1#4530jawwad-ali wants to merge 1 commit into
SystemExit: 1#4530jawwad-ali wants to merge 1 commit into
Conversation
…temExit: 1"
`InitStep._run_init` appended the runner's exception to stderr:
if result.exit_code != 0 and result.exception is not None:
detail = f"{type(result.exception).__name__}: {result.exception}"
stderr = f"{stderr}\n{detail}".strip() if stderr else detail
That branch is written for an unexpected crash, but `typer.Exit(n)` -- how
`specify init` reports every ordinary failure -- surfaces through `CliRunner`
as `result.exception = SystemExit(n)`, so it fired on routine errors too.
`init` prints its diagnostics through Rich to stdout, so `result.stderr` is
empty and the synthesized detail became the ENTIRE stderr, preempting
`execute`'s fallback:
error=(stderr.strip() or stdout.strip() or f"specify init exited ...")
`stdout.strip()` -- which holds the real message -- was never reached.
Reproduced on main with an ordinary typo in `integration:` (which
`InitStep.validate` does not value-check):
validate : []
status : failed | exit_code: 1
result.error : 'SystemExit: 1'
output.stderr: 'SystemExit: 1'
stdout holds : "... lingma, muse, omp, opencode, pi, qodercli, qwen,
rovodev, shai, tabnine, trae, vibe, zcode, zed"
Now excludes only `SystemExit`, so an ordinary non-zero exit falls through to
init's own output while a genuine crash still reports its exception.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
InitStep._run_initappends the runner's exception to stderr:That branch exists for an unexpected crash. But
typer.Exit(n)— howspecify initreports every ordinary failure — surfaces throughCliRunnerasresult.exception = SystemExit(n), so it fires on routine errors too.initprints its diagnostics through Rich to stdout, soresult.stderris empty. The synthesized detail therefore becomes the entirestderr, which preemptsexecute's fallback two frames later:Reproduction on current
main(c173bf1)An ordinary typo in
integration:— whichInitStep.validatedoes not value-check:So the workflow author is told
SystemExit: 1while the list of valid integrations sits unread in stdout. The same applies to any ordinary init failure — aproject:directory that already exists, an unusable script type.It also leaks into workflow data:
steps.<id>.output.stderris the string"SystemExit: 1", so a downstream step reading it gets the sentinel rather than a diagnosis.Fix
Exclude only
SystemExit, preserving the branch for genuine crashes:After the fix the same input reports init's own message, and a
RuntimeErrorescaping the runner still yieldsRuntimeError: boom inside init.Verification
upstream/main→ 16 passed with the fix.RuntimeErrorstill surfaces. It passes both before and after by design: it guards preserved behaviour rather than proving the fix.tests/test_workflows.py: 20 failed / 959 passed vs a clean-mainbaseline of 20 failed / 957 passed — no new failures (the 20 are the known Windowsos.replaceflakiness in that file).uvx ruff@0.15.0 check src tests→ cleanBehaviour change, disclosed:
errorandoutput.stderrfor a failing init step change from"SystemExit: 1"to init's own captured output (which includes its banner, since init writes to stdout). Successful steps are untouched, and no existing test asserted the old sentinel.Written with assistance from Claude Code. Bug found, reproduced, and verified by me on current
main.🤖 Generated with Claude Code